<?  
/************************************************** 
Session - библиотека функций для работы с сессиями  
***************************************************/


// Устанавливаем базовые настройки сессий (при старте сессии их можно изменить) 

// pr(cfg());
// pr(DATA_DIR);	

Session::options(array(
	
	// 'session.save_path' - Папка для сохранения файлов сессий. По умолчанию в Linux - /tmp  (!!! проверяйте существование и права записи папки)
	'session.save_path' => DATA_DIR.'/sessions',
	//'session.save_path' => $_SERVER['DOCUMENT_ROOT'] . '../sessions/',
	
	
	// 'session.cookie_lifetime' – время жизни куки сессии в секундах, 
	// 	поумолчанию = 0, т.е. "пока браузер не будет закрыт")
		'session.cookie_lifetime'	=> 60*60*24*14, // 14 дней
	
	// 'session.gc_maxlifetime' - Промежуток времени (в секундах), по истечении которого файл сессии считаются неактуальным и уничтожаются. Отсчет начинается с момента последнего обращения пользователя в текущем сеансе (с изменением файла сессии). По умолчанию - 1440 сек = 24 мин. 
		'session.gc_maxlifetime' => 60*60*24*14,  // 14 дней
	
	// 'session.use_cookies' – использовать ли куки для хранения сессий?
	'session.use_cookies' => 1,
	
	
	// 'session.use_only_cookies' - использовать только куки и не передавать ID сессии в URL (могут быть проблемы у пользователей при работе через прокси-сервер)
	'session.use_only_cookies' => 1,
	
	
	// 'session.use_trans_sid' - разрешить передавать идентификатра сессии через URL ?
	'session.use_trans_sid' => 1, 
	
	// session.cookie_domain - домен, для которого устанавливать куки
	
	 //'session.cookie_domain' => 
		//$_SERVER['HTTP_HOST'],
	
		// здесь делаем доступным сессии для текущего домена с www и без www (включая субдомены) (возможно, C ЛОКАЛЬНЫМИ ХОСТАМИ БЕЗ ТОЧКИ НЕ РАБОТАЕТ)
		//'.'. str_replace(array('^www.','^'),'', '^' .$_SERVER['HTTP_HOST']),
		
	// имя переменной ID сессии для использования в URL или Cookie. По умолчанию PHPSESSID
	// 'session.name'=> 'PHPSESSID',
	
	// проверять USER-AGENT (идентификатора браузера) и не возобновлять (очищать) сессию при изменении USER-AGENT
		'check_user_agent' 	=> true,
		
		// проверять IP адрес посетителя и не возобновлять (очищать) сессию при изменении IP адреса
		'check_user_ip'	 	=> false,
	
	));
	
	
 	

class Session{

	// флаг - указывает, что сессия уже открыта и проверена
	static $opened=false;

	// опции для работы сессий
	static $options=array();
	
	// первоначально-установленные опции сессий 
	static $base_options=array();
	
	// корректно стартует сессию
	static function start($params=null)
	{
		// return;
		// pr(Session::started());
		//$a=$b;
		
		// уже открыли и проверили сессию ранее в этом классе
		if(Session::$opened) return true;
		
		// открываем сессию, если она ещё не стартовала вне этого класса
		if (!Session::started())
		{
			
			
			// For some reason, MSIE gets confused when session_start() is called and the Content-disposition has been set to "attachment".  The only solution:
			// if(strpos($_SERVER['HTTP_USER_AGENT'],"MSIE")) 
			//		session_cache_limiter('public'); 

			// стартуем сессию
			session_start();
			// pr_(session_id());
			

			
			// если клиент передал некорректный session_id, то перегенерируем его
			if (!preg_match("/^[0-9a-z]{15,50}$/i", $sid=session_id())) 
			{
				// Output a warning about the messed up session-id.
				trigger_error("Некорректный session_id $sid, генерируем его заново");
				session_regenerate_id();
			}
			// pr(session_id());
		
		} //end if (!Session::started())
		
		
		/* проверяем сессию на безопасность */
			
		$session_ok=true;
		
		// привязываем сессию к ID браузера 
		if (	!empty(self::$options['check_user_agent'])
			&&  !empty($_SESSION['HTTP_USER_AGENT'])
			&&  $_SESSION['HTTP_USER_AGENT'] != $_SERVER['HTTP_USER_AGENT']
			) 
			{
				trigger_error("USER_AGENT сессии ($_SESSION[HTTP_USER_AGENT]) и текущего браузера ($_SERVER[HTTP_USER_AGENT]) не совпадают. Cтартуем новую сессию.");
				$session_ok=false;
			}
		
		// привязываем сессию к IP адресу (могут быть проблемы у пользователей с динамическим IP или работающими через прокси)
		if  (  	!empty(self::$options['check_user_ip'])
			&& 	!empty($_SESSION['REMOTE_ADDR'])
			&& 	$_SESSION['REMOTE_ADDR'] != $_SERVER['REMOTE_ADDR']
			) 
			{
				trigger_error("IP-адрес сессии ($_SESSION[REMOTE_ADDR]) и текущего запроса ($_SERVER[REMOTE_ADDR]) не совпадают. Cтартуем новую сессию.");
				$session_ok=false;
			}
			
		if (!$session_ok) {
			session_regenerate_id();
			session_unset();
		}
		// сохраняем дополнительные опознавательные параметры пользователя 
		$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR']; 
		$_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; 
		
		
		
		// продлеваем время жизни сессии каждые 60 секунд (если ничего не писать в сессию, то её время жизни не продлевается)	
		if	( !isset($_SESSION['SESSION_LAST_ACCESS']) 
				OR (time() - $_SESSION['SESSION_LAST_ACCESS']) > 60 
			) 
			$_SESSION['SESSION_LAST_ACCESS'] = time(); 
		
		// pr($_SESSION);
		return true;
		
	} //end function start()
	
	
	// Session::start_existing() - стартует сессию, только если она уже начата ранее (таким образом избекаем старта сесси для поисковых роботов и для незалогиненых пользователей)
	static function start_existing()
	{
		if (Session::exists())
		{
			Session::start(); return true;
		}
		else 
			return  false; 
		
	}
	
	// проверяет - отправил ли клиент корректный ID сессии (т.е. сессия была начата ранее)
	static function exists()
	{
		// ~todo: проверять $session_id на корректность
		
		$sname  = session_name();
		$sid	= val($_COOKIE[$sname], val($_GET[$sname]));
		if(!$sid) return;
		$sfile 	= ini_get('session.save_path'). '/sess_'. $sid;
		
		if (strlen($sid)>3 && file_exists($sfile))
		{
			 return true;
		}
		else 
		{
			 trigger_error("Не найден файл сессии $sfile соответствующий куке $sname=$sid .");
			 setcookie($sname, '', 0);
			 //unset($_COOKIE[$sname]);
			 //pr($sid);
			 return false;	
		}	 
		
	}
	
	// проверяет, была ли уже открыта сессия ранее 
	static function started()
	{
		return isset($_SESSION);
		//return !(session_id()=="")
		//return isset($_SESSION['session_started']))
	}
	
	// ???
	static function restart()
	{
		unset($_COOKIE[session_name()]);
		session_start(); 
	}
	
	// записывает значение в сессию (хранилище данных пользователя на сервере)
	static function set($name, $value=null)	
	{		
		Session::start();
		
		if(is_array($name))
			foreach ($name AS $key=>$val)
				Session::set($key, $val);
		else
		$_SESSION[(string)$name] = $value;	
		// TODO: нужно протестировать - будут ли сохраняться многуровневые массивы с цифровыми ключами 
	}
	
	// извлекает значение из сессии
	static function get($name)
	{		
		Session::start_existing();
		return isset($_SESSION[$name]) ? 
					$_SESSION[$name] : null ;
	}
	
	// проверяет - установлено ли значение переменной в сессии
	static function is_set($name)
	{
		Session::start_existing();
		return isset($_SESSION[$name]); 
	}
	
	// удаляет значение из сессии
	static function del($name)	
	{
		Session::start_existing();
		unset($_SESSION[$name]);	
	}
	
	// очищение сессии
	static function destroy()	
	{
		//pr(Session::start_existing());
		
		//if (!Session::start_existing()) return;
		// Session::start_existing() OR return;
		Session::start_existing();
		
		// обнуляем переменные сессии
		session_unset();
		$_SESSION = array();
				
		// удаляем файл сессии и закрываем текущую сессию
		session_destroy();	
		
		// очищаем ID сессии в куках клиента
		setcookie(session_name(),"",1,'/');

	}
	
/*** 
	Session::set_options -  устанавливает опции для работы сессий
	
	Если это - первый вызов, то опции также сохраняются как базоввые, так что после последующих вызовов Session::set_options() базовые опции можно восстановить с помощью reset_options();
	
	Возвращает старые опции.
***/	
	static function options($options=array())
	{
		
		// старые опции возвращаем 
		$old = self::$options;
		// если ничего не нужно устанавливает - возврат
		if(!$options) return $old;
		// если это первый вызов и базовые опции ещё не заданы - сохраняем в них текущий массив опций
		if(!self::$base_options)  self::$base_options = $options;
		
		// прибавляем к текущим опциям - заданные
		self::$options = array_merge(self::$options, $options);
		
		// устанавливаем системные опции сесий через ini_set
		foreach ($options as $opt_name => $opt_value)
			if (strstr($opt_name,'session.'))
			{
				if ($opt_name=='session.save_path' && 
					! File::is_writable_dir($opt_value)
					) 
				{
					if (!file_exists($opt_value) 
						&& strstr($opt_value, DATA_DIR)
						)
						mkdir($opt_value, 0666);
					
					if(! File::is_writable_dir($opt_value))
						trigger_error('session.save_path "'.$opt_value.'" is not writeble dir!  ' , E_USER_WARNING  );
					
				
				}
				ini_set($opt_name, $opt_value);  
			}
		// возвращаем старый массив опций
		return $old;
	}
	
	// восстанавливает базовые опции сессий
	static function reset_options()
	{
		self::options(self::$base_options);
	}
	
	// ???
	// завершает сессию и записыввает данные в файл сессии
	static function close()	
	{
		// записываем и закрываем сессию пользователя 
		session_write_close();
		
		// очищаем куку сессии у клиента
		setcookie(session_name(),"",time()-1000);
	}
	
	


	
}
